﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Input;
using ERTesting.Model.Config;
using ERTesting.Model.Result;
using GalaSoft.MvvmLight;
using ERTesting.Dao;
using System.Linq;
using System.Collections.ObjectModel;
using System;
using GalaSoft.MvvmLight.Command;
using TestEngine;
using TestEngine.Script;

namespace ERTesting.Gui.ViewModel.Config
{
    /// <summary>
    /// This class contains properties that a View can data bind to.
    /// <para>
    /// Use the <strong>mvvminpc</strong> snippet to add bindable properties to this ViewModel.
    /// </para>
    /// <para>
    /// You can also use Blend to data bind with the tool's support.
    /// </para>
    /// <para>
    /// See http://www.galasoft.ch/mvvm/getstarted
    /// </para>
    /// </summary>
    public class ScenarioViewModel : ViewModelBase
    {
        #region Private Members

        
        private  readonly ITestConfigService _configService;
        private readonly ITestResultService _resultService;
        private ScriptState _lastRun;
        private bool _lazyLoadedInputs = false;
        private bool _lazyLoadedResults = false;

        private ObservableCollection<TestParameterViewModel> _inputs;

        private const string InputPropertyName = "Inputs";

        /// <summary>
        /// The <see cref="Results" /> property's name.
        /// </summary>
        public const string ResultsPropertyName = "Results";

        private ObservableCollection<ScenarioResultViewModel> _results = new ObservableCollection<ScenarioResultViewModel>();

        /// <summary>
        /// The <see cref="Result" /> property's name.
        /// </summary>
        public const string ResultPropertyName = "Result";

        private ScenarioResultViewModel _result = null;

        /// <summary>
        /// The <see cref="Result2" /> property's name.
        /// </summary>
        public const string Result2PropertyName = "Result2";

        private ScenarioResultViewModel _result2 = null;

        /// <summary>
        /// The <see cref="Outputs" /> property's name.
        /// </summary>
        public const string OutputsPropertyName = "Outputs";

        private ObservableCollection<TestParameterViewModel> _outputs = new ObservableCollection<TestParameterViewModel>();

        #endregion Private Members

        #region Public Properties

        public Scenario Scenario
        {
            get;
            private set;
        }

        public string DisplayName
        {
            get
            {
                return Scenario.ScenarioId;
            }
        }

        /// <summary>
        /// The <see cref="IsRunning" /> property's name.
        /// </summary>
        public const string IsRunningPropertyName = "IsRunning";

        private bool _isRunning = false;

        /// <summary>
        /// Gets the IsRunning property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public bool IsRunning
        {
            get
            {
                return _isRunning;
            }

            set
            {
                if (_isRunning == value)
                {
                    return;
                }

                _isRunning = value;

                // Update bindings, no broadcast
                RaisePropertyChanged(IsRunningPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="TestProgress" /> property's name.
        /// </summary>
        public const string TestProgressPropertyName = "TestProgress";

        private int _testProgress = 0;

        /// <summary>
        /// Gets the TestProgress property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public int TestProgress
        {
            get
            {
                return _testProgress;
            }

            set
            {
                if (_testProgress == value)
                {
                    return;
                }

                _testProgress = value;

                // Update bindings, no broadcast
                RaisePropertyChanged(TestProgressPropertyName);
            }
        }

        /// <summary>
        /// Gets the Result2 property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public ScenarioResultViewModel Result2
        {
            get
            {
                return _result2;
            }

            set
            {
                if (_result2 == value)
                {
                    return;
                }
                _result2 = value;

                // Update bindings, no broadcast
                RaisePropertyChanged(Result2PropertyName);
            }
        }

        /// <summary>
        /// Gets the Result1 property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public ScenarioResultViewModel Result
        {
            get
            {
                return _result;
            }

            set
            {
                if (_result == value)
                {
                    return;
                }
                _result = value;

                // Update bindings, no broadcast
                RaisePropertyChanged(ResultPropertyName);
            }
        }

        /// <summary>
        /// Gets the Results property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public ObservableCollection<ScenarioResultViewModel> Results
        {
            get
            {
                if(!_lazyLoadedResults)
                {
                    LoadResults();
                }
                return _results;
            }

            set
            {
                if (_results == value)
                {
                    return;
                }
                _results = value;

                // Update bindings, no broadcast
                RaisePropertyChanged(ResultsPropertyName);
            }
        }




        /// <summary>
        /// Gets the Outputs property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public ObservableCollection<TestParameterViewModel> Outputs
        {
            get
            {
                if (!_lazyLoadedInputs)
                {
                    LoadParameters();
                }
                return _outputs;
            }

            private set
            {
                if (_outputs == value)
                {
                    return;
                }
                _outputs = value;

                // Update bindings, no broadcast
                RaisePropertyChanged(OutputsPropertyName);
            }
        }


        public ObservableCollection<TestParameterViewModel> Inputs
        {
            get
            {
                if (!_lazyLoadedInputs) 
                {
                    LoadParameters();
                }
                return _inputs;
            }
            private set
            {
                _inputs = value;
                
                RaisePropertyChanged(InputPropertyName);
            }
        }
        #endregion Public Properties

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the ScenarioViewModel class.
        /// </summary>
        public ScenarioViewModel(Scenario scenario, ITestConfigService configService, ITestResultService resultService)
        {
            Scenario = scenario;
            _configService = configService;
            _resultService = resultService;
        }

        #endregion Constructors

        #region Private Methods

        private void LoadParameters()
        {
            _lazyLoadedInputs = true;
            ViewModelLocator viewModelLocator = ViewModelLocator.Instance;
            _configService.GetInputParametersForScenario(Scenario, (results, e) => 
            {
                if (results == null)
                {
                    throw new ApplicationException("Failed to load scenario input paramters.", e);
                }
               Inputs = new ObservableCollection<TestParameterViewModel>(
                    from r in results select viewModelLocator.GetViewModel<TestParameterViewModel>("parameter",r));
            }, false);
            _configService.GetOutputParametersForScenario(Scenario, (results, e) =>
                                                                        {
                                                                            if(results == null)
                                                                            {
                                                                                throw new ApplicationException("Failed to load scenario output paramters.", e);

                                                                            }
                                                                            Outputs = new ObservableCollection<TestParameterViewModel>(
                                                                                 from r in results select viewModelLocator.GetViewModel<TestParameterViewModel>("parameter", r));

                                                                        });
        }


        private void LoadResults()
        {
            _lazyLoadedResults = true;
            ViewModelLocator viewModelLocator = ViewModelLocator.Instance;

            _resultService.GetAllResults(Scenario, (results, e) =>
                                                       {
                                                           if(results == null)
                                                           {
                                                               throw new ApplicationException("There was an error fetching results.", e); 
                                                           }
                                                           if (results.Any())
                                                           {
                                                               Results =
                                                                   new ObservableCollection<ScenarioResultViewModel>(
                                                                       from r in results
                                                                       select
                                                                           viewModelLocator.GetViewModel
                                                                           <ScenarioResultViewModel>("scenarioResult", r));
                                                               Result = Results.Last();
                                                           }
                                                       });
        }

        #endregion Private Methods

        #region Commands


        private ICommand _runScenario;
        public ICommand RunScenario
        {
            get
            {
                if (_runScenario == null)
                {
                    _runScenario = new RelayCommand<MouseEventArgs>((args) =>
                    {
                        BackgroundWorker backgroundWorker = new BackgroundWorker();
                        backgroundWorker.WorkerReportsProgress = true;
                        backgroundWorker.DoWork += (sender, eventArgs) =>
                                                       {
                                                           IsRunning = true;
                                                           ERTestEngine erTestEngine = new ERTestEngine();
                                                           var inputs = Inputs.ToDictionary(i => i.Range, i => i.Value);
                                                           var outputs = Outputs.ToDictionary(o => o.Range, o => string.Empty);
                                                           ScriptState scriptState = erTestEngine.RunScript(
                                                               TestScript.Load("Resources/DefaultScript.xml"),inputs,outputs,
                                                               backgroundWorker);
                                                           eventArgs.Result =
                                                               scriptState;
                                                       };
                        backgroundWorker.ProgressChanged += ProgressUpdate;
                        backgroundWorker.RunWorkerCompleted += (sender, eventArgs) =>
                                                                   {
                                                                       IsRunning = false;
                                                                       ScriptState scriptState = eventArgs.Result as ScriptState;
                                                                       if(scriptState == null)
                                                                       {
                                                                           throw new ApplicationException("The script execution resulted  in no state.");
                                                                       }
                                                                       ScenarioResult scenarioResult = new ScenarioResult(_resultService.GetNewResultId(),scriptState.StartTime, (scriptState.EndTime - scriptState.StartTime).TotalSeconds, scriptState.IsFailed, false, Result2!= null ? Result2.ScenarioResult.Id:"", Scenario);
                                                                       IEnumerable<TestOutput> testOutputs = from o in scriptState.Outputs select new TestOutput(o.Key, o.Value);
                                                                       scenarioResult.Initialise(testOutputs);
                                                                       ScenarioResultViewModel viewModel = ViewModelLocator
                                                                                                                            .
                                                                                                                            Instance
                                                                                                                            .
                                                                                                                            GetViewModel
                                                                                                                            <
                                                                                                                                ScenarioResultViewModel
                                                                                                                                >
                                                                                                                            ("scenarioResult",
                                                                                                                             scenarioResult);

                                                                       Results
                                                                           .
                                                                           Add
                                                                           (viewModel);
                                                                       Result
                                                                           =
                                                                           viewModel;
                                                                       _resultService.GetDefaultBaselineResult(scenarioResult, (baseline, ex2) =>
                                                                       {
                                                                           if (baseline != null)
                                                                           {
                                                                              /* scenarioResult
                                                                                   .
                                                                                   BaselineId
                                                                                   =
                                                                                   baseline
                                                                                       .
                                                                                       Id;*/
                                                                               viewModel
                                                                                   .
                                                                                   Baseline
                                                                                   = ViewModelLocator
                                                                                       .
                                                                                       Instance
                                                                                       .
                                                                                       GetViewModel
                                                                                       <
                                                                                           ScenarioResultViewModel
                                                                                           >
                                                                                       ("scenarioResult",
                                                                                    baseline);
                                                                           }

                                                                       });
                                                                       _resultService.SaveResult(scenarioResult, (result, ex)=>
                                                                                                                     {
                                                                                                                         if(result == null)
                                                                                                                         {
                                                                                                                             throw new ApplicationException("Could not save the scenario result.", ex);
                                                                                                                         }
                                                                                                                        
                                                                                                                     });

                                                                   };
                        backgroundWorker.RunWorkerAsync();

                    });
                }
                return _runScenario;
            }
        }

        protected ScriptState LastRun
        {
            get {
                return _lastRun;
            }
            set
            {
                var oldValue = _lastRun;
                _lastRun = value;
                if(oldValue!= null)
                {
                    oldValue = null;
                    System.GC.Collect();
                }
            }
        }

        void ProgressUpdate(object sender, ProgressChangedEventArgs e)
        {
            TestProgress = e.ProgressPercentage;
        }

        #endregion Commands
    }
}